#ifndef __EPSILON__STRINGS_H__
#define __EPSILON__STRINGS_H__

#if (defined (WIN32) || defined (WIN64))

// String function security is already a design consideration,
//   no point having the compiler blab on about things we do not
//     need/want to know!
#ifndef  _CRT_SECURE_NO_WARNINGS
# define _CRT_SECURE_NO_WARNINGS
#endif

//
// Win32 or Win64
//
#  define HAS_STRLWR     // Has stlwr

#  define HAS_ITOA       // Has itoa

#  define HAS_STRICMP    // Has stricmp
#  undef  HAS_STRCASECMP // Does not need this UNIX hack...

#  define HAS_STRNICMP    // Has strnicmp
#  undef  HAS_STRNCASECMP // Does not need this UNIX hack...

#  undef  HAS_SNPRINTF   // Does not have snprintf
#  define HAS__SNPRINTF  // Windows *DOES* have _snprintf, which
                         // does the exact same thing.
#  undef  HAS_SNTPRINTF  // Does not have sntprintf
#  define HAS__SNTPRINTF // Windows *DOES* have _sntprintf, which

#  undef  HAS_VSNPRINTF  // Does not have vsnprintf
#  define HAS__VSNPRINTF // Windows *DOES* have _vsnprintf, which
                         // does the exact same thing.

#  undef  HAS_VSSCANF    // Does not have vsscanf

#else

//
// UNIX
//
#  undef  HAS_STRLWR      // Does not have strlwr

#  undef  HAS_ITOA        // Does not have itoa

#  undef  HAS_STRICMP     // Does not have stricmp
#  define HAS_STRCASECMP  // UNIX *DOES* have strcasecmp, which
                          // does the exact same thing.

#  undef  HAS_STRNICMP    // Does not have strnicmp
#  define HAS_STRNCASECMP // UNIX *DOES* have strncasecmp, which
                          // does the exact same thing.

#  define HAS_SNPRINTF    // Has snprintf
#  undef  HAS__SNPRINTF   // Don't need this Windows hack...

#  undef  HAS_SNTPRINTF   // Does not have sntprintf
#  define HAS__SNTPRINTF  // UNIX *DOES* have _sntprintf, which
                          // does the exact same thing.

#  define HAS_VSNPRINTF   // Has vsnprintf
#  undef  HAS__VSNPRINTF  // Don't need this Windows hack...

#  define HAS_VSSCANF     // Has vsscanf

#endif /* WIN32 or WIN64 */


#include "tchar_epsilon.h" // Cross-platform friendly TCHAR.H substitute
#include <stdio.h>
#include <stdarg.h>



// Setup snprintf (if not provided)
#ifndef HAS_SNPRINTF
#  ifdef HAS__SNPRINTF
#    define snprintf _snprintf
#  else
#    pragma error ("No snprintf routine available!")
#  endif
#endif


// Setup sntprintf (if not provided)
#ifndef HAS_SNTPRINTF
#   ifdef HAS__SNTPRINTF
#     define sntprintf _sntprintf
#   endif
#endif


// Setup vsnprintf (if not provided)
#ifndef HAS_VSNPRINTF
#  ifdef HAS__VSNPRINTF
#    define vsnprintf _vsnprintf
#    define vswprintf _vsnwprintf
#  else
#    pragma error ("No vsnprintf routine available!")
#    pragma error ("No vsnwprintf routeine available!")
#  endif
#endif


// Setup stricmp (if not provided)
#ifndef HAS_STRICMP
#  ifdef HAS_STRCASECMP
#    define stricmp strcasecmp
#  else
#    pragma error ("No stricmp equiv. routine available!")
#  endif
#endif


// Setup strnicmp (if not provided)
#ifndef HAS_STRNICMP
#  ifdef HAS_STRNCASECMP
#    define strnicmp strncasecmp
#  else
#    pragma error ("No strnicmp equiv. routine available!")
#  endif
#endif



// Setup itoa (if not provided)
#ifndef HAS_ITOA
/* NOTE: This function is slow since it uses
         sprintf... Try to avoid itoa unless
         you really need it :) */
static char*
itoa (int value, char* string, int radix = 10)
{
  if (radix != 10)
    sprintf (string, "Unsupported Radix!");
  else
    sprintf (string, "%d", value);

  return string;
}
#endif


// Setup strlwr (if not provided)
#ifndef HAS_STRLWR
#include <ctype.h>
static
TCHAR*
strlwr (TCHAR* szString)
{
  if (! szString)
    return _T ('\0');

  TCHAR* pszString = szString;

  while (*pszString)
    *pszString++ = tolower (*pszString);

  return szString;
};
#endif


// Setup vsscanf (if not provided)
#ifndef HAS_VSSCANF
#include <ctype.h>
/**
 * This is a hacked implementation of vsscanf, because some
 * platforms don't provide it (i.e. Win32).
 *
 * It works by identifying common format specifiers and using
 * %n to identify how many chars were read from the input string.
 *
 * On every pass, we skip n-many chars in the input string so that
 * we're scanning the next argument in the list. We also offset
 * the format pointer to the next argument.
 *
 * This is by no means a perfect solution, but it gets the job done.
 *
 *   Currently supported format types are:
 *
 *      %d,  %u,  %g, %f
 *     %ld, %lu, %lg
 *     %hd, %hu
 *      %s,  %c
 *
 *   * Asterisks (to suppress assignment) are not supported...
 *
**/
static
int
vsscanf (const char *s, const char *fmt, va_list va)
{
  int k;       // Accumulates all sscanf return values...

  k = 0;
  while (true) // Loop continuously until a NULL char is found in fmt.
  {
    if (*fmt == '%')
    {
      int n = 0;   // Number of chars read

      // Int
      if (fmt [1] == 'd')
      {
        k += sscanf (s, "%d%n", va_arg (va, int*), &n);
        s += n; fmt += 2;
      }

      // Unsigned Int
      else if (fmt [1] == 'u')
      {
        k += sscanf (s, "%u%n", va_arg (va, int*), &n);
        s += n; fmt += 2;
      }

      // Float
      else if (fmt [1] == 'f' ||
               fmt [1] == 'g')
      {
        k += sscanf (s, "%f%n", va_arg (va, float*), &n);
        s += n; fmt += 2;
      }


      // Long
      else if (fmt [1] == 'l')
      {
        // (Long) Int
        if (fmt [2] == 'd')
        {
          k += sscanf (s, "%ld%n", va_arg (va, long*), &n);
          s += n; fmt += 3;
        }

        // (Long) Unsigned Int
        else if (fmt [2] == 'u')
        {
          k += sscanf (s, "%lu%n", va_arg (va, long*), &n);
          s += n; fmt += 3;
        }

        // (Long) Double
        else if (fmt [2] == 'g')
        {
          k += sscanf (s, "%lg%n", va_arg (va, double*), &n);
          s += n; fmt += 3;
        }

        else
        {
          //Assert (false, "Unsupported vsscanf format type (long)!");
          return k;
        }
      }


      // Half
      else if (fmt [1] == 'h')
      {
        // (Short) Int
        if (fmt [2] == 'd')
        {
          k += sscanf (s, "%hd%n", va_arg (va, short*), &n);
          s += n; fmt += 3;
        }

        // (Short) Unsigned Int
        else if (fmt [2] == 'u')
        {
          k += sscanf (s, "%hu%n", va_arg (va, short*), &n);
          s += n; fmt += 3;
        }

        else
        {
          //Assert (false, "Unsupported vsscanf format type (half)!");
          return k;
        }
      }


      // String
      else if (fmt [1] == 's')
      {
        k += sscanf (s, "%s%n", va_arg (va, char*), &n);
        s += n; fmt += 2;
      }


      // Character
      else if (fmt [1] == 'c')
      {
        k += sscanf (s, "%c%n", va_arg (va, char*), &n); // n should ALWAYS be 1...
        s += n; fmt += 2;
      }


      else
      {
        //Assert (false, "Unsupported vsscanf format type!");
        return k;
      }
    }


    // Skip Whitespace
    else if (isspace ((unsigned char)*fmt))
    {
      while (isspace ((unsigned char)*s))
        s++;

      fmt++;
    }

    // Terminate on a NULL char.
    else if (*fmt == '\0')
    {
      return k;
    }
  }
}
#endif


/**
 * Appends the formatted string to the specified output string
 *
**/
static
void
appendf (TCHAR* szAppend, const TCHAR* szFormat, ...)
{
  TCHAR szText [2048];
  va_list argv;

  if (szFormat == NULL)
    return;

  va_start (argv, szFormat);
    _vsntprintf (szText, 2047, szFormat, argv);
  va_end (argv);

  *(szText + 2047) = _T ('\0');

  _tcsncat (szAppend, szText, 2048);
}

/**
 * Replaces all instances of in with out
 *
**/
static
void
replacef (TCHAR* szReplace, const TCHAR in, const TCHAR out)
{
  if (! szReplace)
    return;

  size_t i;
  size_t iStrLen = _tcslen (szReplace);

  TCHAR* pszBufferPos = szReplace;

  for (i = 0; i < iStrLen; i++) {
    if (*pszBufferPos == in)
      *pszBufferPos = out;

    pszBufferPos++;
  }
}

/**
 * This is similar to printf, sprintf, etc... except that it
 * takes no input TCHAR* and instead returns a temporary TCHAR*
 *
 * It's useful when you want to pass a formatted TCHAR* string
 * to a function, without a temporary TCHAR array and a call to
 * sprintf on another line.
 *
 * 		NOTE: Absolutely NOT re-entrant!
 *
**/
static
const TCHAR*
stringf (const TCHAR* szFormat, ...)
{
  static TCHAR szText [2048];
  va_list argv;

  if (szFormat == NULL)
    return _T (" ");
/*
#ifdef _DEBUG
  int out_len = 0;

  va_start (argv, szFormat);
    out_len += vfprintf (E_DEV_NULL, szFormat, argv);
  va_end (argv);

  Assert (out_len < 8192, _T ("The string returned by stringf will")
                          _T ("be truncated!"));
#endif
*/

  va_start (argv, szFormat);
    _vsntprintf (szText, 2047, szFormat, argv);
  va_end (argv);

  *(szText + 2047) = _T ('\0');

//
// Ignore warning 4172 on MSVC.
//
//  * We KNOW we're returning a temporary, it's safe...
//
#ifdef _MSC_VER
#  pragma warning( push )
#  pragma warning( disable : 4172 )
#endif

  return szText;

#ifdef _MSC_VER
#  pragma warning( pop )
#endif
}


/**
 * A custom strstr implementation that's case insensitive
 *
**/
static
TCHAR*
stristr (const TCHAR* szString, const TCHAR* szPattern)
{
  TCHAR *pptr, *sptr, *start;
  size_t slen,  plen;

  /* While string length is not shorter than pattern length */
  for (start = (TCHAR *) szString, pptr = (TCHAR *) szPattern,
                                   slen = _tcslen (szString),
                                   plen = _tcslen (szPattern);
           slen >= plen;
               start++, slen--) {

    /* Find start of pattern in string */
    while (_totupper (*start) != _totupper (*szPattern)) {
      start++;
      slen--;       /* If pattern longer than string */

      if (slen < plen)
        return (NULL);
    }

    sptr = start;
    pptr = (TCHAR *) szPattern;


    while (_totupper (*sptr) == _totupper (*pptr)) {
      sptr++;
      pptr++;       /* If end of pattern then pattern was found */

      if (_T ('\0') == *pptr)
        return (start);
    }
  }

  return (NULL);
}


#endif /* __EPSILON__STRINGS_H__ */
